﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace BeerScanOTron_Barcode
{
    public partial class UserDetail : Form
    {
        private StateContext _stateContext;
        private Drinker _drinker;
        private BindingSource _bindingSource;
        private string _beerCode;
        private Timer _timer;

        public UserDetail(string cardID)
        {
            InitializeComponent();

            // There is a weird bug in VS that shows link label tabstop as false in the UI but doesn't 
            // honor that when it generates the code so we have to do it here instead.
            lblEditUser.TabStop = false;
            lblDeleteUser.TabStop = false;

            // Init timer.
            _timer = new Timer();

            // Create a state binding context.
            _stateContext = new StateContext();

            // Card id is new or known but should always be provided.
            _drinker = LoadDrinker(cardID);
            _bindingSource = new BindingSource();
            _bindingSource.DataSource = _drinker;

            // Bind the state and drinker objects to form.
            BindFields();
        }

        private void BindFields()
        {
            // Card ID
            txtCardID.DataBindings.Add(new Binding("Text", _bindingSource, "CardId"));

            // First name.
            txtFirstName.DataBindings.Add(new Binding("Enabled", _stateContext, "FirstNameEnabled"));
            txtFirstName.DataBindings.Add(new Binding("Text", _bindingSource, "FirstName"));

            // Last name.
            txtLastName.DataBindings.Add(new Binding("Enabled", _stateContext, "LastNameEnabled"));
            txtLastName.DataBindings.Add(new Binding("Text", _bindingSource, "LastName"));

            // Save button.
            btnSave.DataBindings.Add(new Binding("Enabled", _stateContext, "SaveButtonEnabled"));
            btnSave.DataBindings.Add(new Binding("Text", _stateContext, "SaveButtonText", false, DataSourceUpdateMode.OnPropertyChanged));

            // Instruction text.
            lblInstruction.DataBindings.Add(new Binding("Text", _stateContext, "InstructionLabelText", false, DataSourceUpdateMode.OnPropertyChanged));

            // Form.
            this.DataBindings.Add(new Binding("Text", _stateContext, "FormCaption"));
            this.DataBindings.Add(new Binding("BackColor", _stateContext, "DialogBackground"));
            
            // Link labels.
            lblEditUser.DataBindings.Add("Enabled", _stateContext, "LblEditUserEnabled");
            lblEditUser.DataBindings.Add("Text", _stateContext, "LblEditUserCaption");
            lblDeleteUser.DataBindings.Add("Enabled", _stateContext, "LblDeleteUserEnabled");
            lblDeleteUser.DataBindings.Add("Text", _stateContext, "LblDeleteUserCaption");
        }

        private Drinker LoadDrinker(string cardId)
        {
            using (ScanOTronDB ctx = new ScanOTronDB())
            {
                int beerMax = ctx.Settings.Select(s => s.BeerMax).FirstOrDefault();

                // Try to find the barcode id.
                Drinker drinker = ctx.Drinkers.Where(u => u.CardId == cardId).SingleOrDefault();

                // Set value and state from the results.
                if (drinker == null)
                {
                    _stateContext.State = new NewUserState();
                    return new Drinker(cardId);
                }
                else
                {
                    // Check drink history for the day.
                    List<DispenserInfo> dispInfos = ctx.DispenserInfoes
                        .Where(d => d.CardId == drinker.CardId && 
                                   (DbFunctions.TruncateTime(d.ServeDate) == DateTime.Today.Date))
                        .ToList();

                    if (dispInfos.Count >= beerMax)
                    {
                        // Oops we are over the limit. Time to deny!
                        _stateContext.State = new DenyUserState();
                        _timer.Tick += _timer_Tick;
                        _timer.Interval = 3000; 
                        _timer.Start();
                    }
                    else
                    {
                        _stateContext.State = new ExistingUserState();
                    }

                    return drinker;
                }
            }
        }

        private void SaveData()
        {
            // Make sure all fields have been filled in.
            if (!String.IsNullOrWhiteSpace(txtFirstName.Text) && !String.IsNullOrWhiteSpace(txtLastName.Text))
            {
                using (ScanOTronDB ctx = new ScanOTronDB())
                {
                    if (_stateContext.State is NewUserState)
                    {
                        ctx.Drinkers.Add(_drinker);
                    }
                    else if (_stateContext.State is EditExistingUserState)
                    {
                        // For edits we have to pull the user since our user comes from a different context.
                        Drinker currentValue = ctx.Drinkers.Where(d => d.CardId == _drinker.CardId).SingleOrDefault();
                        ctx.Drinkers.Attach(currentValue);
                        ctx.Entry(currentValue).CurrentValues.SetValues(_drinker);
                    }
                    else if (_stateContext.State is DeleteUserState)
                    {
                        // Get user from current context and delete both user and dispenser records.
                        Drinker currentValue = ctx.Drinkers.Where(d => d.CardId == _drinker.CardId).SingleOrDefault();
                        ctx.Drinkers.Remove(currentValue);
                        ctx.DispenserInfoes.RemoveRange(ctx.DispenserInfoes.Where(d => d.CardId == _drinker.CardId));
                    }

                    // Save record.
                    ctx.SaveChanges();
                }

                // Close user detail form.
                this.Close();
                DialogResult = DialogResult.OK;
            }
            else
            {
                MessageBox.Show("Please fill in all text fields before saving");
            }
        }

        private void DispenseBeerAndClose()
        {
            // Make sure we have a beer barcode.
            if (_beerCode != null && _beerCode.First() != '1')
            {
                // Create a dispenser record.
                DispenserInfo dInfo = new DispenserInfo()
                {
                    BrandID = Convert.ToInt32(_beerCode),
                    CardId = _drinker.CardId,
                    ServeDate = DateTime.Now
                };

                using (ScanOTronDB ctx = new ScanOTronDB())
                {
                    ctx.DispenserInfoes.Add(dInfo);
                    ctx.SaveChanges();
                }

                // Close user detail form.
                this.Close();
                DialogResult = DialogResult.OK;
            }
            else
            {
                MessageBox.Show(this,
                    "The scanned barcode is not a beer brand barcode.",
                    "Bad Code Type",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Warning);
            }
        }

        private void ToggleEditCancelButton()
        {
            // Toggle between edit state and beer serving state.
            if (_stateContext.State is EditExistingUserState)
            {
                _stateContext.State = new ExistingUserState();
                _stateContext.Update();

                // Reload the original drinker to clean up field edits.
                _drinker = LoadDrinker(_drinker.CardId);
                _bindingSource.ResetBindings(false);
                _drinker.Update();
            }
            else
            {
                _stateContext.State = new EditExistingUserState();
                _stateContext.Update();
            }
        }

        private void _timer_Tick(object sender, EventArgs e)
        {
            this.Close();
            DialogResult = DialogResult.OK;
        }

        #region UI Event handlers

        private void UserDetail_KeyDown(object sender, KeyEventArgs e)
        {
            if (_stateContext.State is ExistingUserState)
            {
                // Add non-control characters to scan buffer if we are in serve mode.
                if (char.IsLetterOrDigit((char)e.KeyCode))
                {
                    _beerCode += e.GetKeyValue();

                    if (_beerCode.Length == 6)
                    {
                        if (_beerCode.ToLower() == "cancel")
                        {
                            this.Close();
                            DialogResult = DialogResult.OK;
                        }
                        else
                        {
                            // Barcode is fully received, dispense beer.
                            DispenseBeerAndClose();
                        }
                    }
                }
            }
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            SaveData();
        }

        private void lblDeleteUser_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            if (MessageBox.Show("Are you sure you want to delete this user?", "Delete", MessageBoxButtons.OKCancel) == DialogResult.OK)
            {
                _stateContext.State = new DeleteUserState();
                SaveData();
            }
        }

        private void lblEditUser_MouseDown(object sender, MouseEventArgs e)
        {
            ToggleEditCancelButton();
        }

        #endregion
    }
}
